#include <stdio.h>
#include <iostream>
#include <math.h>
#ifdef PLATFORM_OSX
#include <mach/mach_time.h>
#include <mach/mach.h>
#include <CoreServices/CoreServices.h>
#else
#include <time.h>
#include <sys/time.h>
#endif

#include "OpenCL/clUtil/clUtil.h"
#include "qmc.h"
#include "Intel/WalkerGroupCPU.h"
#include "OpenCL/WalkerGroupOpenCL.h"

#define SHOW_ITERATION

unsigned int gXDim;
unsigned int gYDim;
unsigned int gZDim;
unsigned int gNumIterations = 1000;
unsigned int gNumWalkers = 1000;
unsigned int gDevNum;
unsigned int gWarmup = 10000;
double gParticleMass = 7294.29935;
double gStepSize = .20;
int gRandomSeed = 11132;
double gLengthConversion = 0.5291772108;
double gEnergyConversion = 219474.6313705;
bool gUseCPU = false;
unsigned int gWalkersPerGroup = 1;
char* gInputFileName = NULL;
char* gOutputFileName = NULL;

double getTime()
{
#ifdef PLATFORM_OSX //Apple just has to be a PITA with timer interfaces
  uint64_t time;
  Nanoseconds ns;

  time = mach_absolute_time();

  ns = AbsoluteToNanoseconds(*(AbsoluteTime*)&time);

  return ((double)*(uint64_t*)&ns) / 1e9;
#else
  struct timeval time;
  
  gettimeofday(&time, NULL);
  return (double)time.tv_sec + (double)time.tv_usec / 1e6;
#endif

}

int main(int argc, char** argv)
{
  WalkerGroup* initialWalker;
  cl_event event;
  unsigned int walkersDone = 0;

  parseArgs(argc, argv);

  if(gInputFileName == NULL && gOutputFileName == NULL)
  {
    printf("To create an initial configuration, use --output-file. To read\n");
    printf("an existing configuration, use --input-file. Use --help for\n");
    printf("more info.\n");
    exit(1);
  }

  if(gInputFileName != NULL && gOutputFileName != NULL)
  {
    printf("Do you want to make an initial configuration or simulate an\n");
    printf("existing one? --output-file does the former, --input-file does\n");
    printf("the latter. You can't do both.\n");
    exit(1);
  }

  //--output-file means create a configuration
  if(gOutputFileName != NULL)
  {
    if(gXDim * gYDim * gZDim < 1000 || gUseCPU == true)
    {
      initialWalker = new WalkerGroupCPU(gXDim, gYDim, gZDim);
    }
    else
    {
      initialWalker = new WalkerGroupOpenCL(gXDim, gYDim, gZDim);
    }

    for(unsigned int i = 0; i < gWarmup; i++)
    {
      initialWalker->compute(&event);
    }

    initialWalker->writeToDisk(gOutputFileName);
  }
  else //--input-file means load a configuration
  {
    unsigned int curWalker;
    double totalEnergy = 0.0;

    //Load walker from disk
    initialWalker = new WalkerGroupCPU(gInputFileName);
    
    //Do as many as you can with multi-walkers
    if(gNumWalkers / gWalkersPerGroup > 0)
    {
      for(curWalker = 0; curWalker < gNumWalkers; curWalker += gWalkersPerGroup)
      {
        WalkerGroup* curWalkers;

        if(gUseCPU == true)
        {
          curWalkers = new WalkerGroupCPU(initialWalker, 
                                          gWalkersPerGroup,
                                          curWalker);
        }
        else
        {
          curWalkers = new WalkerGroupOpenCL(initialWalker, 
                                             gWalkersPerGroup,
                                             curWalker);
        }

        for(unsigned int curIteration = 0; 
            curIteration < gNumIterations; 
            curIteration++)
        {
          curWalkers->compute(&event);
        }

        totalEnergy += curWalkers->getEnergy();

        delete curWalkers;
      }
    }

    //Do the rest
    if(gNumWalkers % gWalkersPerGroup != 0)
    {
      WalkerGroup* curWalkers;

      if(gUseCPU == true)
      {
        curWalkers = 
          new WalkerGroupCPU(initialWalker, 
                             gNumWalkers % gWalkersPerGroup,
                             gNumWalkers - gNumWalkers % gWalkersPerGroup);
      }
      else
      {
        curWalkers = 
          new WalkerGroupOpenCL(initialWalker, 
                                gNumWalkers % gWalkersPerGroup,
                                gNumWalkers - gNumWalkers % gWalkersPerGroup);
      }

      for(unsigned int curIteration = 0; 
          curIteration < gNumIterations; 
          curIteration++)
      {
        curWalkers->compute(&event);
      }

      totalEnergy += curWalkers->getEnergy();

      delete curWalkers;
    }
    printf("Total energy:%lE\n", 
           totalEnergy / gNumWalkers / (gXDim * gYDim * gZDim) / 3.1668153e-6);
  }
}
